<template>
	<div
		ref="container"
		class="w100 h100 relative"
		@drop="dragHandle"
		@dragover="dragoverHandle"
		@dragenter="dragoverHandle"
		@mousedown="(e) => modelClick(e, true)"
	>
		<n-layout has-sider sider-placement="right" class="object" @mousedown.stop="">
			<n-layout-sider
				collapse-mode="transform"
				:collapsed-width="0"
				:width="400"
				:native-scrollbar="false"
				show-trigger="bar"
			>
				<threeObject
					v-bind="{ cameraList, modelList, lightList, active }"
					@click="clickFn"
					@delete="deleteFn"
					@visible="visibleFn"
				/>
			</n-layout-sider>
		</n-layout>

		<!-- 添加或修改部门对话框 -->
		<!-- <el-dialog :title="dialogTitle" v-model="dialog.open" width="500px" append-to-body> </el-dialog> -->
	</div>
</template>

<script setup>
import threeObject from './threeObject.vue';
import {
	useResizeObserver,
	THREE,
	OrbitControls,
	TransformControls,
	rendererFn,
	GLTFloaderFn,
	raycaster,
	ViewHelper,
	clock
} from '@u/threeMinx';

/***
 * props
 * @property {String} transformControlsMode  TransformControls控制器的模式选择（translate：移动模式）,（rotate：旋转模式）
 * @property {Boolean} catchPoint 是否捕捉点坐标，鼠标点击选中的3维坐标
 */
const props = defineProps({
	transformControlsMode: {
		type: String
	},
	catchPoint: {
		type: Boolean
	}
});

/**
 * 设置变换控制器模式
 */
watch(
	() => props.transformControlsMode,
	(val) => {
		transformControls?.setMode(val);
	}
);

let container = shallowRef(null), // 模型渲染的Demo节点
	renderer, //  WebGLRenderer渲染器
	scene, // 场景
	camera, // 相机
	controls, // 控制器
	transformControls, // 变换控制器
	viewHelper, // xyz坐标
	plane; // 基础平面，用于获取鼠标3维坐标

// 组件宽高
let width, height;
useResizeObserver(container, (entries) => {
	width = entries[0].contentRect.width;
	height = entries[0].contentRect.height;
	camera.aspect = width / height;
	camera.updateProjectionMatrix();
	renderer.setSize(width, height);
});

// 初始化生命周期
onMounted(() => {
	init();
});

onBeforeUnmount(() => {
	cancelAnimationFrame(requestAnimationFrameIndex);
	// 释放显存
	renderer?.dispose();
	// 释放内存
	scene?.clear();
	container = null;
	scene = null;
});

/**
 * 场景初始化
 */
const cameraList = ref([]);
function init() {
	// 场景
	scene = new THREE.Scene();
	scene.background = new THREE.Color('#dcdcdc');
	// 相机
	camera = new THREE.PerspectiveCamera(45, container.value.offsetWidth / container.value.offsetHeight, 0.01, 30000);
	camera.position.set(0, 10, 10);
	cameraList.value.push({
		name: '默认相机',
		id: camera.id,
		visible: camera.visible
	});
	scene.add(camera);

	//坐标轴
	const axesHelper = new THREE.AxesHelper(10);
	scene.add(axesHelper);

	// 渲染器
	renderer = rendererFn(container.value);

	// 控制器
	controls = new OrbitControls(camera, renderer.domElement);
	controls.center = new THREE.Vector3();
	controls.target.set(0, 0, 0);

	// 变换控制器
	transformControls = new TransformControls(camera, renderer.domElement);
	transformControls.setMode(props.transformControlsMode);
	// 是否正在拖动
	transformControls.addEventListener('dragging-changed', (event) => {
		controls.enabled = !event.value;
	});
	scene.add(transformControls);

	// xyz视角
	viewHelper = new ViewHelper(camera, container.value);
	viewHelper.controls = controls;

	// 添加默认环境光
	addlightFn();

	// 添加基础平面
	plane = new THREE.Mesh(
		new THREE.PlaneGeometry(10000, 10000),
		new THREE.MeshBasicMaterial({ opacity: 0, transparent: true })
	);
	plane.name = '基础平面';
	plane.rotation.x = -0.5 * Math.PI;
	plane.position.set(0, 0, 0);
	scene.add(plane);

	// 渲染器
	render();
}

/***
 * requestAnimationFrame 渲染
 */
let requestAnimationFrameIndex;
function render() {
	const delta = clock.getDelta();
	requestAnimationFrameIndex = requestAnimationFrame(render);
	// 控制器
	controls?.update();
	// 渲染器渲染
	renderer?.render(scene, camera);
	// xyz视角可视化渲染
	if (viewHelper.animating === true) viewHelper.update(delta);
	renderer.autoClear = false;
	viewHelper?.render(renderer);
	renderer.autoClear = true;
}

/***
 * 获取鼠标与场景中模型相交的坐标点
 */
function modelClick(e, attach = false) {
	if (controls?.enabled) {
		const { left, top, width, height } = renderer.domElement.getBoundingClientRect(),
			mouse = new THREE.Vector2(((e.clientX - left) / width) * 2 - 1, -((e.clientY - top) / height) * 2 + 1);

		raycaster.setFromCamera(mouse, camera);

		const intersects = raycaster.intersectObjects(
			scene.children.filter((item) => {
				if (attach) {
					return item.isModel || item.islightHepler;
				} else {
					return item.isModel || item.islightHepler || item.name === '基础平面';
				}
			})
		);

		if (intersects.length > 0) {
			const { x, y, z } = intersects[0].point;
			if (attach) {
				let data = intersects[0].object;

				// 从下往上查找父类
				while (!(data.isModel || data.islightHepler) && data.parent) {
					data = data.parent;
				}

				// 选中模型
				if (data.isModel) {
					attach && transformControls.attach(data);
					active.id = data.id;
					active.obj = data;
				}

				// 选中灯光
				if (data.islightHepler) {
					attach && transformControls.attach(data.light);
					active.id = data.light.id;
					active.obj = data.light;
				}

				if (props.catchPoint) threePosToGeo(intersects[0].point);
			}
			return { x, y, z };
		} else {
			attach && transformControls.detach();
			active.id = null;
			active.obj = {};
		}
	}
}

function threePosToGeo(point) {
	// 从场景中取得相机的当前位置
	const cameraPosition = new THREE.Vector3();
	cameraPosition.setFromMatrixPosition(camera.matrixWorld);

	// 将点的坐标转换为以相机位置为原点的坐标
	const pointRelative = point.clone().sub(cameraPosition);

	// 计算经度和纬度
	const phi = Math.atan2(pointRelative.z, pointRelative.x),
		theta = Math.asin(pointRelative.y / pointRelative.length());

	// 将弧度转换为以度为单位的角度
	const long = phi * (180 / Math.PI),
		lat = theta * (180 / Math.PI);

	// 计算高度
	const radius = 6371, // 地球半径（单位：米）
		height = pointRelative.length() - radius;

	// 打印经纬度和高度
	console.log('Long: ' + long);
	console.log('Lat: ' + lat);
	console.log('Height: ' + height);
}

/**********************************************模型拖拽添加***********************/
import { JSONParse } from '@/utils';
import { infoModel } from '@a/threeModel';
import { reactive } from 'vue';

/***
 * 添加灯光
 */
const lightList = ref([]);
function addlightFn(
	drayData = {
		title: '环境光',
		isLight: true,
		api: 'AmbientLight'
	},
	position
) {
	// 定义灯光组
	const light = new THREE[drayData.api]('#ffffff', 1);
	light.name = drayData.title;
	light.isLightGroup = true;
	light.colorString = '#ffffff';
	position && light.position.set(position.x, position.y, position.z);
	lightList.value.push({
		name: light.name,
		id: light.id,
		visible: light.visible
	});
	scene.add(light);

	// 添加帮助器
	if (THREE[`${drayData.api}Helper`]) {
		const lightHepler = new THREE[`${drayData.api}Helper`](light);
		lightHepler.islightHepler = true;
		light.lightHeplerId = lightHepler.id;
		scene.add(lightHepler);
		clickFn(light.id);
	}
}

/***
 * 添加模型
 */
const modelList = ref([]);
function addmodelFn(drayData, position) {
	infoModel({ id: drayData.id }).then(({ data }) => {
		GLTFloaderFn.load(data.fileInfo.downloadUrl, (object) => {
			const model = object.scene;
			if (position) model.position.set(position.x, position.y, position.z);
			model.isModel = true;
			model.name = drayData?.title;
			scene.add(model);
			modelList.value.push({
				name: model.name,
				id: model.id,
				visible: model.visible
			});
			clickFn(model.id);
		});
	});
}

// 拖拽到编辑区域里
async function dragHandle(e) {
	e.preventDefault();
	// 子元素的 drop 事件
	e.stopPropagation();
	// 获取拖拽数据
	const drayData = JSONParse(e?.dataTransfer?.getData('data'));

	try {
		const position = modelClick(e);

		// 添加模型
		if (drayData.isModel) addmodelFn(drayData, position);

		// 添加灯光
		if (drayData.isLight) addlightFn(drayData, position);
	} catch (error) {
		console.log(error);
	}
}

// 进入拖拽区域
function dragoverHandle(e) {
	e.preventDefault();
	// 子元素的 drop 事件
	e.stopPropagation();
	if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
}

/****************************************************模型操作，选中、删除、显隐 *****************************/

// 清除场景模型数据
function removeObject(object) {
	if (object.parent === null) return;
	// 材质释放内存
	object.traverse((v) => {
		if (v.type === 'Mesh') {
			v.geometry.dispose();
			v.material.dispose();
		}
	});
	// 移除
	object.parent.remove(object);
}

// 选中模型的 id 和 obj 数据
const active = reactive({
	id: null,
	obj: {}
});
function clickFn(id) {
	active.id = id;
	active.obj = scene.getObjectById(id);
	transformControls.attach(active.obj);
}
function deleteFn(id, i) {
	active.obj = scene.getObjectById(id);
	if (active.obj.isLight) {
		lightList.value.splice(i, 1);
		removeObject(active.obj);
		removeObject(scene.getObjectById(active.obj.lightHeplerId));
		return;
	}
	if (active.obj.isModel) {
		modelList.value.splice(i, 1);
		removeObject(active.obj);
	}
}
function visibleFn(id) {
	active.obj = scene.getObjectById(id);
	active.obj.visible = !active.obj.visible;
	if (active.obj.isLight) {
		const data = scene.getObjectById(active.obj.lightHeplerId);
		data.visible = !data.visible;
	}
}
</script>

<style lang="scss" scoped>
.object {
	position: absolute;
	top: 0;
	right: 0;
	height: 100%;
	overflow: visible;
	:deep() {
		.n-layout-scroll-container {
			overflow: visible;
		}
	}
}
</style>
